﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Security.Cryptography;
using System.Text;
using System.IO;
using System.Reflection;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Text.RegularExpressions;
using System.Drawing;
using System.Drawing.Imaging;

namespace blackhouse.Web.FasterFiles
{
    public class FasterFilesProvider
    {

        #region Fields

#if DEBUG
        private ICompressOutput cssCompressor = null;
#else
        private ICompressOutput cssCompressor = new CssCompressor();
#endif
        private Page currentPage = null;

        private string eTag;

        private string eTagRequest;

#if DEBUG
        private static ICompressOutput jsCompressor = null;
#else
        private static ICompressOutput jsCompressor = null;// new JSCompressor();
#endif
        private static Dictionary<Guid, FasterFilesStore> stored = new Dictionary<Guid, FasterFilesStore>();

        private bool useAxdExtensions = true;

        #endregion

        #region Properties

        private string extensionCss { get { return (UseAxdExtensions) ? "css.axd" : "css"; } }

        private string extensionJs { get { return (UseAxdExtensions) ? "js.axd" : "js"; } }

        public bool UseAxdExtensions
        {
            get { return this.useAxdExtensions; }
            set { this.useAxdExtensions = value; }
        }

        #endregion

        #region Methods

        #region Public

        public void AddCssFile(Type senderType, string file)
        {
            AddFile(FasterFilesType.Css, senderType, file);
        }

        public void AddFile(FasterFilesType type, Type senderType, string file)
        {
            this.addFile(type, senderType, file);
        }

        public void AddJsFile(Type senderType, string file)
        {
            //nierozwiązany błąd dla jquery// dodanie pliku z jquery nie działa :(
            AddFile(FasterFilesType.JavaScript, senderType, file);
        }

        public void GetContent(FasterFilesType type, Guid id, HttpResponse response)
        {
            GetContent(type, id, response, null);
        }

        public void GetContent(FasterFilesType type, Guid id, HttpResponse response, ICompressOutput compressor)
        {
            this.setETags(type, id);
            this.setHeadersForAll(response, this.eTag);
            if (this.trySetNotModified(response))
                response.End();

            response.Cache.SetAllowResponseInBrowserHistory(true);
            this.trySetGZip();
            StringBuilder buildResponse = new StringBuilder();
            foreach (FasterFilesStoreItem item in getStore(id).Where(si => si.Type == type))
                writeToOutputStream(item, buildResponse);
            string responseTxt;
            if (compressor != null)
                responseTxt = compressor.CompressOutput(buildResponse.ToString());
            else
                responseTxt = buildResponse.ToString();

            response.Write(responseTxt);

        }

        public void GetCssContent(Guid id, HttpResponse response)
        {
            GetContent(FasterFilesType.Css, id, response, cssCompressor);
        }

        public Guid GetId()
        {
            if (this.checkHandler())
                return this.getUniqueID();
            else return Guid.Empty;
        }

        public void GetImgContent(string assembly, string file, string imageFormat, HttpResponse response)
        {
            Assembly ass = Assembly.ReflectionOnlyLoad(assembly);
            using (Stream st = ass.GetManifestResourceStream(file + '.' + imageFormat))
            using (MemoryStream mst = new MemoryStream())
            {
                if (st != null)
                {
                    using (Bitmap bp = new Bitmap(st))
                    {
                        ImageFormat format = null;
                        switch (imageFormat)
                        {
                            case "gif": format = ImageFormat.Gif; break;
                            case "png": format = ImageFormat.Png; break;
                            default: format = ImageFormat.Jpeg; break;
                        }
                        bp.Save(mst, format);
                        mst.WriteTo(response.OutputStream);
                    }
                }
            }
        }

        public void GetJsContent(Guid id, HttpResponse response)
        {
            GetContent(FasterFilesType.JavaScript, id, response, jsCompressor);
        }

        public bool HaveContent(FasterFilesType type)
        {
            Guid id = this.getUniqueID();
            FasterFilesStore store = this.getStore(id);
            return store.Any(ff => ff.Type == type);
        }

        public bool HaveCssContent()
        {
            return HaveContent(FasterFilesType.Css);
        }

        public bool HaveJsContent()
        {
            return HaveContent(FasterFilesType.JavaScript);
        }

        #endregion

        #region Private

        private void addCssReference()
        {
            if (this.currentPage.Header == null)
                throw new InvalidOperationException("empty head");
            HtmlLink link = new HtmlLink();
            link.Attributes.Add("rel", "stylesheet");
            link.Attributes.Add("type", "text/css");
            link.Href = string.Format("/{0}/FasterFiles.{1}", getUniqueID(), this.extensionCss);
            this.currentPage.Header.Controls.Add(link);
        }

        private void addFile(FasterFilesType type, Type locationType, string file)
        {
            if (this.checkHandler())
            {
                Guid id = this.getUniqueID();
                FasterFilesStore store = this.getStore(id);
                FasterFilesStoreItem item = new FasterFilesStoreItem();
                item.Type = type;
                item.SenderType = locationType;
                item.File = file;
                if (!store.Contains(item))
                    store.Add(item);
            }
        }

        private void addJsReference()
        {
            if (this.currentPage.Header != null)
            {
                this.currentPage.Header.Controls.Add(new Literal() { Text = "<script src=\"/" + getUniqueID() + "/FasterFiles." + this.extensionJs + "\" type=\"text/javascript\"></script>" });
            }
            else
            {
                if (!this.currentPage.ClientScript.IsClientScriptIncludeRegistered("fasterFiles"))
                    this.currentPage.ClientScript.RegisterClientScriptInclude("fasterFiles", string.Format("/{0}/FasterFiles." + this.extensionJs, getUniqueID()));
            }
        }

        private bool checkHandler()
        {
            if (currentPage == null)
                currentPage = HttpContext.Current.Handler as Page;
            return currentPage != null;
        }

        private void clearOverdated()
        {
            TimeSpan time = TimeSpan.FromMinutes(20);
            List<Guid> guids = new List<Guid>();
            foreach (KeyValuePair<Guid, FasterFilesStore> kvp in stored.Where(kvp => kvp.Value.IsExpired(time)))
            {
                //stored.Remove(kvp.Key);
                guids.Add(kvp.Key);
            }
            foreach (Guid guid in guids)
            {
                stored.Remove(guid);
            }
        }

        protected void addReferences()
        {
            //if (this.HaveCssContent())
            this.addCssReference();
            //if (this.HaveJsContent())
            this.addJsReference();

            this.clearOverdated();
        }

        private static string decodeUtf8(string s_Input)
        {
            Encoding iso = Encoding.GetEncoding("ISO-8859-2");
            Encoding utf8 = Encoding.UTF8;
            byte[] utfBytes = utf8.GetBytes(s_Input);
            byte[] isoBytes = Encoding.Convert(utf8, iso, utfBytes);
            return iso.GetString(isoBytes);
        }

        private FasterFilesStore getStore(Guid id)
        {
            if (!stored.ContainsKey(id))
                stored.Add(id, new FasterFilesStore());
            return stored[id];
        }

        private Guid getUniqueID()
        {
            string itemsKey = "uniqueID";
            Guid id = Guid.Empty;
            if (!currentPage.Items.Contains(itemsKey))
            {
                id = Guid.NewGuid();
                currentPage.Items.Add(itemsKey, id);
                this.addReferences();
				currentPage.PreRender += new EventHandler(currentPage_PreRender);
            }
            else
                id = (Guid)currentPage.Items[itemsKey];
            return id;

        }

		void currentPage_PreRender(object sender, EventArgs e) {
			throw new NotImplementedException();
		}

        private bool isGZipSupported()
        {
            string acceptEncoding = HttpContext.Current.Request.Headers["Accept-Encoding"];
            if (!string.IsNullOrEmpty(acceptEncoding) &&
                 (acceptEncoding.Contains("gzip") || acceptEncoding.Contains("deflate")))
                return true;
            return false;
        }

        private void setCacheHeaders(int hashOfResponse)
        {
            HttpResponse response = HttpContext.Current.Response;
            HttpRequest request = HttpContext.Current.Request;

            response.Cache.VaryByHeaders["Accept-Encoding"] = true;
            response.Cache.SetExpires(DateTime.Now.ToUniversalTime().AddDays(7));
            response.Cache.SetMaxAge(new TimeSpan(7, 0, 0, 0));
            response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
            string etag = "\"" + hashOfResponse.ToString() + "\"";
            string incomingEtag = request.Headers["If-None-Match"];
            response.Cache.SetETag(etag);
            if (String.Compare(incomingEtag, etag) == 0)
            {
                response.Clear();
                response.StatusCode = (int)System.Net.HttpStatusCode.NotModified;
                response.SuppressContent = true;
            }
        }

        private void setETags(FasterFilesType type, Guid guid)
        {
            StringBuilder buildETag = new StringBuilder();
            foreach (FasterFilesStoreItem item in this.getStore(guid).Where(ffsi => ffsi.Type == type).OrderBy(ffsi => ffsi.File))
            {
                buildETag.Append(item.SenderType.Assembly.GetName().Version.ToString());
                buildETag.Append(item.File);
            }
            this.eTag = buildETag.ToString().GetMd5();
            this.eTagRequest = HttpContext.Current.Request.Headers["If-None-Match"];

        }

        private void setHeadersForAll(HttpResponse response, string eTag)
        {
            response.Clear();
            response.Cache.SetCacheability(HttpCacheability.Public);
            response.Cache.SetExpires(DateTime.Now.AddDays(1));
            response.Cache.SetETag(eTag);
        }

        private void setIsNotModified(HttpResponse response)
        {
            response.StatusCode = 304;
            response.StatusDescription = "Not Modified";
            response.Headers.Add("Content-Length", "0");
        }

        private void trySetGZip()
        {
            if (isGZipSupported())
            {
                HttpResponse response = HttpContext.Current.Response;

                string acceptEncoding = HttpContext.Current.Request.Headers["Accept-Encoding"];
                if (acceptEncoding.Contains("gzip"))
                {
                    response.Filter = new System.IO.Compression.GZipStream(response.Filter, System.IO.Compression.CompressionMode.Compress);
                    response.AppendHeader("Content-Encoding", "gzip");
                }
                else
                {
                    response.Filter = new System.IO.Compression.DeflateStream(response.Filter, System.IO.Compression.CompressionMode.Compress);
                    response.AppendHeader("Content-Encoding", "deflate");
                }
            }
        }

        private bool trySetNotModified(HttpResponse response)
        {
            if (this.eTag == this.eTagRequest)
            {
                this.setIsNotModified(response);
                return true;
            }
            else return false;
        }
        private static void writeToOutputStream(FasterFilesStoreItem item, StringBuilder response)
        {
            Assembly assembly = item.SenderType.Assembly;
            Stream resource = assembly.GetManifestResourceStream(item.File);
            if (resource != null)
            {

                using (StreamReader reader = new StreamReader(resource))
                {
                    //string utf8 = reader.ReadToEnd();
                    //string unicode = decodeUtf8(utf8);
                    response.AppendLine(reader.ReadToEnd());
                }

            }
        }

        #endregion

        #endregion
    }
}